/* winsio.c */

/********************/
/* SERIAL-INTERFACE */
/* for MS-Windows   */
/* (C) 2003 by NAH6 */
/********************/

/*------------*/
/*  INCLUDES  */
/*------------*/

#include <memory.h>
#include <windows.h>

#include "winsio.h"

/*--------------*/
/*  PROTOTYPES  */
/*--------------*/

static signed short sio__txchunk (struct sio_data *dseg, unsigned char term, \
                                  unsigned char dcd, unsigned char ring,     \
                                  unsigned char slow);

static signed short sio__txdelay (struct sio_data *dseg, unsigned char term, \
                                  unsigned char dcd, unsigned char ring,     \
                                  unsigned short delay);

static signed short sio__rxcheck (struct sio_data *dseg, unsigned char term, \
                                  unsigned char dcd, unsigned char ring,     \
                                  signed short retval);
static signed short sio__txcheck (struct sio_data *dseg, unsigned char term, \
                                  unsigned char dcd, unsigned char ring,     \
                                  signed short retval);

static signed short sio__rxclear (struct sio_data *dseg);
static signed short sio__txclear (struct sio_data *dseg);

static signed short sio__rxerror (struct sio_data *dseg, unsigned char clear);
static signed short sio__savesioerror (struct sio_data *dseg);

static signed short sio__chkctrlwires (struct sio_data *dseg,                 \
                                       unsigned char term, unsigned char dcd, \
                                       unsigned char ring);

static signed short  sio__rilsync   (struct sio_data *dseg);
static void CALLBACK sio__rilresult (DWORD index, HRESULT cmdid, \
                                     const void *data, DWORD dlen, DWORD par);

/*-------------*/
/*  VARIABLEN  */
/*-------------*/

static const DWORD sio__baudcaps[K_SIO_BAUDRATES] =
{
  BAUD_300,  BAUD_600,   BAUD_1200,  BAUD_2400,  BAUD_4800,
  BAUD_9600, BAUD_19200, BAUD_38400, BAUD_57600, BAUD_115200
};

static const DWORD sio__baudrate[K_SIO_BAUDRATES] =
{
  CBR_300,  CBR_600,   CBR_1200,  CBR_2400,  CBR_4800,
  CBR_9600, CBR_19200, CBR_38400, CBR_57600, CBR_115200
};

static const DWORD sio__errorflag[K_SIO_ERRORFLAGS] =
{
  CE_IOE,      CE_BREAK,   CE_FRAME,
  CE_RXPARITY, CE_OVERRUN, CE_RXOVER
};

/*----*/

static const TCHAR sio__obxregname1[]  = \
             TEXT ("Software\\Microsoft\\Obex\\Settings");
static const TCHAR sio__obxregname2[]  = TEXT ("ServerState");

static const TCHAR sio__rildllname[]   = TEXT ("ril.dll");
static const TCHAR sio__rilfktn_init[] = TEXT ("RIL_Initialize");
static const TCHAR sio__rilfktn_end[]  = TEXT ("RIL_Deinitialize");
static const TCHAR sio__rilfktn_regs[] = TEXT ("RIL_GetRegistrationStatus");

/*############################################################################*/

/*--------------*/
/*  OPEN/CLOSE  */
/*--------------*/
                                                           /* Open Sio-Driver */
signed short sio_open (struct sio_data *dseg, const struct sio_config *cfg)
{
  unsigned char rtscts, xonxoff, parbuf[2];
  unsigned long rxbuflen,txbuflen;
  COMMPROP sioprop;
  TCHAR devname[6];
  signed short n;
  DCB siomode;
  HRESULT r;
  DWORD d,e;
  LONG g;
  BOOL b;

  /* Check pointer */
  if ((dseg == NULL) || (cfg == NULL))
    return E_SIO_NULLPTR;

  /* Setup variables */
  memcpy (&(dseg->cfg), cfg, sizeof (struct sio_config));
  dseg->rxerror   = 0x00;
  dseg->rxcacherd = 0x0000;
  dseg->rxcachewr = 0x0000;
  dseg->txcacheda = 0x00;
  dseg->txcacherd = 0x0000;
  dseg->txcachewr = 0x0000;

  /* Open obx-dev */
  if (cfg->obxport > K_SIO_MAXOBXPORTS)
    return E_SIO_OPEN_OBXNUM;
  if (cfg->obxport)
  {
    g = RegOpenKeyEx (HKEY_CURRENT_USER, sio__obxregname1, 0, KEY_ALL_ACCESS, \
                      &(dseg->obxreghnd));
    if (g)
      return E_SIO_OPEN_OBXREGOP;
    d = REG_NONE;
    e = 4L;
    g = RegQueryValueEx (dseg->obxreghnd, sio__obxregname2, NULL, &d, \
                         (void *)(&(dseg->obxregval)), &e);
    if (g)
    {
      n = E_SIO_OPEN_OBXREGRD;
      goto err_exit11;
    }
    if (d != REG_DWORD)
    {
      n = E_SIO_OPEN_OBXREGTYP;
      goto err_exit11;
    }
    if (e != 4)
    {
      n = E_SIO_OPEN_OBXREGLEN;
      goto err_exit11;
    }
    if (dseg->obxregval)
    {
      d = 0L;
      g = RegSetValueEx (dseg->obxreghnd, sio__obxregname2, 0, REG_DWORD, \
                         (void *)&d, 4);
      if (g)
      {
        n = E_SIO_OPEN_OBXREGWR;
        goto err_exit11;
      }
      devname[0] = 0x4F;  /* O */
      devname[1] = 0x42;  /* B */
      devname[2] = 0x58;  /* X */
      devname[3] = cfg->obxport + 0x2F;
#ifndef _WIN32_WCE
      devname[4] = 0;
#else
      devname[4] = 0x3A;  /* : */
      devname[5] = 0;
#endif
      dseg->obxdevhnd = CreateFile (devname, 0, FILE_SHARE_READ |          \
                                    FILE_SHARE_WRITE, NULL, OPEN_EXISTING, \
                                    FILE_ATTRIBUTE_SYSTEM, NULL);
      if (dseg->obxdevhnd == INVALID_HANDLE_VALUE)
      {
        n = E_SIO_OPEN_OBXDEV;
        goto err_exit10;
      }
      d = 0L;
      b = DeviceIoControl (dseg->obxdevhnd, 0x01040008L, 0, 0, 0, 0, &d, 0);
      if (!b)
      {
        n = E_SIO_OPEN_OBXDCTL;
        goto err_exit9;
      }
    }
  }

  /* Open ril-dll */
  if (cfg->rilport > K_SIO_MAXRILPORTS)
  {
    n = E_SIO_OPEN_RILNUM;
    goto err_exit8;
  }
  if (cfg->rilport)
  {
    dseg->rildllhnd = LoadLibrary (sio__rildllname);
    if (dseg->rildllhnd == NULL)
    {
      n = E_SIO_OPEN_RILDLL;
      goto err_exit8;
    }
    dseg->ril_init = (void *)(GetProcAddress (dseg->rildllhnd, \
                                              sio__rilfktn_init));
    if (dseg->ril_init == NULL)
      goto err_rilfktn;
    dseg->ril_end = (void *)(GetProcAddress (dseg->rildllhnd, \
                                              sio__rilfktn_end));
    if (dseg->ril_end == NULL)
      goto err_rilfktn;
    dseg->ril_regs = (void *)(GetProcAddress (dseg->rildllhnd, \
                                              sio__rilfktn_regs));
    if (dseg->ril_regs == NULL)
    {
      err_rilfktn:
      n = E_SIO_OPEN_RILFKTN;
      goto err_exit7;
    }
    dseg->riljobevt = CreateEvent (NULL, TRUE, 0, NULL);
    if (dseg->riljobevt == NULL)
    {
      n = E_SIO_OPEN_RILEVT;
      goto err_exit7;
    }
    r = (dseg->ril_init)(cfg->rilport, sio__rilresult, NULL, 0, (DWORD)dseg, \
                         &(dseg->rilusrhnd));
    if (r < 0)
    {
      n = E_SIO_OPEN_RILINIT;
      goto err_exit6;
    }
    n = sio__rilsync (dseg);
    if (n < 0)
    {
      n = E_SIO_OPEN_RILSYNC1 + 1 - n;
      goto err_exit5;
    }
  }

  /* Open serial port */
  if (cfg->portnum == K_SIO_PORTNUM_HANDLE)
    dseg->siohnd = cfg->porthnd;
  else
  {
    if (cfg->portnum >= K_SIO_MAXCOMPORTS)
    {
      n = E_SIO_OPEN_PORTNUM;
      goto err_exit4;
    }
    devname[0] = 0x43;  /* C */
    devname[1] = 0x4F;  /* O */
    devname[2] = 0x4D;  /* M */
    devname[3] = cfg->portnum + 0x31;
#ifndef _WIN32_WCE
    devname[4] = 0;
#else
    devname[4] = 0x3A;  /* : */
    devname[5] = 0;
#endif
    dseg->siohnd = CreateFile (devname, GENERIC_READ | GENERIC_WRITE, 0, NULL, \
                               OPEN_EXISTING, 0, NULL);
  }
  if (dseg->siohnd == INVALID_HANDLE_VALUE)
  {
    n = E_SIO_OPEN_CREATE;
    goto err_exit4;
  }

  /* Check properties */
  b = GetCommProperties (dseg->siohnd, &sioprop);
  if (!b)
  {
    n = E_SIO_OPEN_GETPROP;
    goto err_exit3;
  }
  if (!(sioprop.dwServiceMask & SP_SERIALCOMM) \
   || ((sioprop.dwProvSubType != PST_RS232)    \
    && (sioprop.dwProvSubType != PST_MODEM)))
  {
    n = E_SIO_OPEN_DEVTYPE;
    goto err_exit3;
  }
  if (!(sioprop.wSettableData & DATABITS_8) \
   || !(sioprop.wSettableStopParity & PARITY_NONE)
#ifndef _WIN32_WCE
   || !(sioprop.wSettableStopParity & STOPBITS_10)
#endif
     )
  {
    n = E_SIO_OPEN_NO8N1;
    goto err_exit3;
  }
  if (cfg->baudrate >= K_SIO_BAUDRATES)
    goto err_baud;
  if (!(sioprop.dwSettableBaud & sio__baudcaps[cfg->baudrate]) \
   && !(sioprop.dwSettableBaud & BAUD_USER))
  {
    err_baud:
    n = E_SIO_OPEN_BAUDRATE;
    goto err_exit3;
  }
  rtscts = 0;
  xonxoff = 0;
  if (cfg->flowctrl == K_SIO_FLOWCTRL_RTSCTS)
  {
    rtscts = 1;
#ifndef _WIN32_WCE
    if (!(sioprop.dwProvCapabilities & PCF_RTSCTS))
      goto err_flow;
#endif
  }
  else if (cfg->flowctrl == K_SIO_FLOWCTRL_XONXOFF)
  {
    xonxoff = 1;
    if (!(sioprop.dwProvCapabilities & PCF_XONXOFF))
      goto err_flow;
  }
  else if (cfg->flowctrl != K_SIO_FLOWCTRL_NONE)
  {
    err_flow:
    n = E_SIO_OPEN_FLOWCTRL;
    goto err_exit3;
  }
#ifndef _WIN32_WCE
  if (cfg->usedtr && !(sioprop.dwProvCapabilities & PCF_DTRDSR))
  {
    n = E_SIO_OPEN_USEDTR;
    goto err_exit3;
  }
  if (cfg->usedsr && !(sioprop.dwProvCapabilities & PCF_DTRDSR))
  {
    n = E_SIO_OPEN_USEDSR;
    goto err_exit3;
  }
  if (cfg->usedcd && !(sioprop.dwProvCapabilities & PCF_RLSD))
  {
    n = E_SIO_OPEN_USEDCD;
    goto err_exit3;
  }
#endif

  /* Setup sio-buffer */
  rxbuflen = K_SIO_RXBUFSIZE;
  if (sioprop.dwMaxRxQueue && (sioprop.dwMaxRxQueue < rxbuflen))
    rxbuflen = sioprop.dwMaxRxQueue;
  txbuflen = K_SIO_TXBUFSIZE;
  if (sioprop.dwMaxTxQueue && (sioprop.dwMaxTxQueue < txbuflen))
    txbuflen = sioprop.dwMaxTxQueue;
  b = SetupComm (dseg->siohnd, rxbuflen, txbuflen);
  if (!b)
  {
    n = E_SIO_OPEN_SETBUFFER;
    goto err_exit3;
  }

  /* Setup sio-mode */
  memset (&siomode, 0, sizeof (DCB));
  siomode.DCBlength     = sizeof (DCB);
  siomode.BaudRate      = sio__baudrate[cfg->baudrate];
  siomode.fBinary       = TRUE;
  siomode.fParity       = FALSE;
  siomode.fOutxCtsFlow  = rtscts;
  siomode.fOutxDsrFlow  = FALSE;
  siomode.fDtrControl   = (cfg->usedtr == K_SIO_DTRUSE_DIRECT) ? \
                          DTR_CONTROL_DISABLE:DTR_CONTROL_ENABLE;
  siomode.fDsrSensitivity = cfg->usedsr;
  siomode.fTXContinueOnXoff = TRUE;
  siomode.fOutX         = xonxoff;
  siomode.fInX          = xonxoff;
  siomode.fErrorChar    = FALSE;
  siomode.fNull         = FALSE;
  siomode.fRtsControl   = rtscts ? RTS_CONTROL_HANDSHAKE:RTS_CONTROL_ENABLE;
  siomode.fAbortOnError = TRUE;
  siomode.XonLim        = (unsigned short)(rxbuflen >> 2);
  siomode.XoffLim       = (unsigned short)(rxbuflen >> 2);
  siomode.ByteSize      = 8;
  siomode.Parity        = NOPARITY;
  siomode.StopBits      = ONESTOPBIT;
  siomode.XonChar       = 0x11;  /* Ctrl-Q */
  siomode.XoffChar      = 0x13;  /* Ctrl-S */
  siomode.ErrorChar     = 0x2A;  /* Star-* */
  siomode.EofChar       = 0x1A;  /* Ctrl-Z */
  siomode.EvtChar       = 0x1B;  /* Escape */
  b = SetCommState (dseg->siohnd, &siomode);
  if (!b)
  {
    n = E_SIO_OPEN_SETMODE;
    goto err_exit3;
  }

  /* Setup sio-timer */
  n = sio_threads (dseg, 0);
  if (n < 0)
    goto err_exit3;

  /* Activate ext-device */
  if (cfg->usedtr == K_SIO_DTRUSE_DIRECT)
  {
    Sleep (K_SIO_RESTIME1);
    b = EscapeCommFunction (dseg->siohnd, SETDTR);
    if (!b)
    {
      n = E_SIO_OPEN_SETDTR;
      goto err_exit3;
    }
    Sleep (K_SIO_RESTIME2);
  }
  else if (cfg->portnum != K_SIO_PORTNUM_HANDLE)
    Sleep (K_SIO_RESTIME2);

  /* Power-check ext-device */
  d = 0L;
  b = GetCommModemStatus (dseg->siohnd, &d);
  if (!b)
  {
    n = E_SIO_OPEN_GETLINE;
    goto err_exit2;
  }
  if ((cfg->usedsr) && !(d & MS_DSR_ON))
  {
    n = E_SIO_OPEN_NODSR;
    goto err_exit2;
  }
#ifndef _WIN32_WCE
  if (rtscts && !(d & MS_CTS_ON))
  {
    n = E_SIO_OPEN_NOCTS;
    goto err_exit2;
  }
#endif
  if ((cfg->usedcd) && (d & MS_RLSD_ON))
  {
    n = E_SIO_OPEN_DCDON;
    goto err_exit2;
  }

  /* Clear rx-cache/errors */
  d = 0L;
  b = ClearCommError (dseg->siohnd, &d, NULL);
  if (!b)
  {
    n = E_SIO_OPEN_CLRRXERR;
    goto err_exit2;
  }
  b = PurgeComm (dseg->siohnd, PURGE_RXCLEAR);
  if (!b)
  {
    n = E_SIO_OPEN_CLRRXBUF;
    goto err_exit2;
  }

  /* Open ril-dev */
  if (cfg->rilport)
  {
    devname[0] = 0x52;  /* R */
    devname[1] = 0x49;  /* I */
    devname[2] = 0x4C;  /* L */
    devname[3] = cfg->rilport + 0x30;
#ifndef _WIN32_WCE
    devname[4] = 0;
#else
    devname[4] = 0x3A;  /* : */
    devname[5] = 0;
#endif
    dseg->rildevhnd = CreateFile (devname, GENERIC_READ | GENERIC_WRITE,    \
                                  FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, \
                                  OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM, NULL);
    if (dseg->rildevhnd == INVALID_HANDLE_VALUE)
    {
      n = E_SIO_OPEN_RILDEV;
      goto err_exit2;
    }
    d = 0L;
    e = 0L;
    b = DeviceIoControl (dseg->rildevhnd, 0x03000314L, 0, 0, &d, 4, &e, 0);
    if (!b)
    {
      n = E_SIO_OPEN_RILDCTL;
      goto err_exit1;
    }
    parbuf[0] = 0x84;
    parbuf[1] = 0x00;
    b = DeviceIoControl (dseg->siohnd, 0xAAAA5679L, parbuf, 2, 0, 0, 0, 0);
    if (!b)
    {
      n = E_SIO_OPEN_RILSCTL;
      d = 0L;
      e = 0L;
      DeviceIoControl (dseg->rildevhnd, 0x03000318L, 0, 0, &d, 4, &e, 0);
      err_exit1:
      CloseHandle (dseg->rildevhnd);
      err_exit2:
      if (cfg->usedtr)
        EscapeCommFunction (dseg->siohnd, CLRDTR);
      err_exit3:
      if (cfg->portnum != K_SIO_PORTNUM_HANDLE)
        CloseHandle (dseg->siohnd);
      err_exit4:
      if (cfg->rilport)
      {
        sio__rilsync (dseg);
        err_exit5:
        (dseg->ril_end)(dseg->rilusrhnd);
        err_exit6:
        CloseHandle (dseg->riljobevt);
        err_exit7:
        FreeLibrary (dseg->rildllhnd);
      }
      err_exit8:
      if (cfg->obxport)
      {
        if (dseg->obxregval)
        {
          RegSetValueEx (dseg->obxreghnd, sio__obxregname2, 0, REG_DWORD, \
                         (void *)(&(dseg->obxregval)), 4);
          d = 0L;
          DeviceIoControl (dseg->obxdevhnd, 0x01040004L, 0, 0, 0, 0, &d, 0);
          err_exit9:
          CloseHandle (dseg->obxdevhnd);
          err_exit10:
          RegSetValueEx (dseg->obxreghnd, sio__obxregname2, 0, REG_DWORD, \
                         (void *)(&(dseg->obxregval)), 4);
        }
        err_exit11:
        RegCloseKey (dseg->obxreghnd);
      }
      return n;
    }
  }

  /* Setup locks */
  InitializeCriticalSection (&(dseg->winlock));
  InitializeCriticalSection (&(dseg->errlock));
  return 0;
}

/*----*/

signed short sio_close (struct sio_data *dseg)            /* Close Sio-Driver */
{
  signed short n,m;
  HRESULT r;
  DWORD d,e;
  LONG g;
  BOOL b;

  /* Check pointer */
  if (dseg == NULL)
    return E_SIO_NULLPTR;

  /* Remove locks */
  DeleteCriticalSection (&(dseg->errlock));
  DeleteCriticalSection (&(dseg->winlock));

  /* Close ril-dev */
  n = 0;
  if (dseg->cfg.rilport)
  {
    d = 0L;
    e = 0L;
    b = DeviceIoControl (dseg->rildevhnd, 0x03000318L, 0, 0, &d, 4, &e, 0);
    if (!b)
      n = E_SIO_CLOSE_RILDCTRL;
    b = CloseHandle (dseg->rildevhnd);
    if ((!b) && (n >= 0))
      n = E_SIO_CLOSE_RILDEV;
  }

  /* Disable ext-device */
  if (dseg->cfg.usedtr == K_SIO_DTRUSE_DIRECT)
  {
    b = EscapeCommFunction (dseg->siohnd, CLRDTR);
    if ((!b) && (n >= 0))
      n = E_SIO_CLOSE_SETDTR;
  }

  /* Close serial port */
  if (dseg->cfg.portnum != K_SIO_PORTNUM_HANDLE)
  {
    b = CloseHandle (dseg->siohnd);
    if ((!b) && (n >= 0))
      n = E_SIO_CLOSE_HANDLE;
  }

  /* Close ril-dll */
  if (dseg->cfg.rilport)
  {
    m = sio__rilsync (dseg);
    if ((m < 0) && (n >= 0))
      n = E_SIO_CLOSE_RILSYNC1 + 1 - m;
    r = (dseg->ril_end)(dseg->rilusrhnd);
    if ((r < 0) && (n >= 0))
      n = E_SIO_CLOSE_RILEND;
    b = CloseHandle (dseg->riljobevt);
    if ((!b) && (n >= 0))
      n = E_SIO_CLOSE_RILEVT;
    b = FreeLibrary (dseg->rildllhnd);
    if ((!b) && (n >= 0))
      n = E_SIO_CLOSE_RILDLL;
  }

  /* Close obx-dev */
  if (dseg->cfg.obxport)
  {
    if (dseg->obxregval)
    {
      g = RegSetValueEx (dseg->obxreghnd, sio__obxregname2, 0, REG_DWORD, \
                         (void *)(&(dseg->obxregval)), 4);
      if (g && (n >= 0))
        n = E_SIO_CLOSE_OBXREGWR;
      d = 0L;
      b = DeviceIoControl (dseg->obxdevhnd, 0x01040004L, 0, 0, 0, 0, &d, 0);
      if ((!b) && (n >= 0))
        n = E_SIO_CLOSE_OBXDCTL;
      b = CloseHandle (dseg->obxdevhnd);
      if ((!b) && (n >= 0))
        n = E_SIO_CLOSE_OBXDEV;
    }
    g = RegCloseKey (dseg->obxreghnd);
    if (g && (n >= 0))
      n = E_SIO_CLOSE_OBXREGCL;
  }
  return n;
}

/*----*/
                                                           /* Set Thread-Mode */
signed short sio_threads (struct sio_data *dseg, unsigned char multi)
{
  COMMTIMEOUTS siotime;
  unsigned short n;
  BOOL b;

  /* Check pointer */
  if (dseg == NULL)
    return E_SIO_NULLPTR;

  /* Setup sio-timer */
  dseg->threads = multi;
  siotime.ReadIntervalTimeout         = MAXDWORD;
  siotime.WriteTotalTimeoutMultiplier = 0L;
  if (multi)
  { /* serialize driver - no wait inside */
    siotime.ReadTotalTimeoutMultiplier = 0L;
    siotime.ReadTotalTimeoutConstant   = 0L;
    siotime.WriteTotalTimeoutConstant  = 0L;
  }
  else
  { /* single thread - wait inside */
    siotime.ReadTotalTimeoutMultiplier = MAXDWORD;;
    siotime.ReadTotalTimeoutConstant   = K_SIO_RXCHKTIME;
    siotime.WriteTotalTimeoutConstant  = K_SIO_TXCHKTIME;
  }
  b = SetCommTimeouts (dseg->siohnd, &siotime);
  if (!b)
    n = E_SIO_OPEN_SETTIMER;
  else
    n = 0;
  return n;
}

/*----*/
                                                            /* Pulse DTR-line */
signed short sio_pulse (struct sio_data *dseg, unsigned char term, \
                        unsigned char dcd, unsigned char ring)
{
  struct sio_config siocfg;
  signed short n;
  BOOL b;

  /* Check pointer */
  if (dseg == NULL)
    return E_SIO_NULLPTR;

  /* Pulse dtr-line */
  n = 0;
  if (dseg->cfg.usedtr == K_SIO_DTRUSE_DIRECT)
  {
    /* Direct control */
    n = sio__txcheck (dseg, term, dcd, ring, 0);
    if (n < 0)
      return n;
    b = EscapeCommFunction (dseg->siohnd, CLRDTR);
    if (!b)
      return E_SIO_PULSE_SETDTR1;
    n = sio__txdelay (dseg, term, dcd, ring, K_SIO_RESTIME1);
    if (n < 0)
      return n;
    b = EscapeCommFunction (dseg->siohnd, SETDTR);
    if (!b)
      return E_SIO_PULSE_SETDTR2;
    n = sio__txdelay (dseg, term, dcd, ring, K_SIO_RESTIME2);
  }
  else if (dseg->cfg.usedtr == K_SIO_DTRUSE_REOPEN)
  {
    /* Open/close control */
    memcpy (&siocfg, &(dseg->cfg), sizeof (struct sio_config));
    n = sio_close (dseg);
    if (n < 0)
      goto double_close;
    Sleep (K_SIO_RESTIME1);
    n = sio_open (dseg, &siocfg);
    if (n < 0)
    {
      double_close:
      InitializeCriticalSection (&(dseg->winlock));
      InitializeCriticalSection (&(dseg->errlock));
      dseg->cfg.portnum = K_SIO_PORTNUM_HANDLE;
      dseg->cfg.obxport = 0;
      dseg->cfg.rilport = 0;
      dseg->siohnd = INVALID_HANDLE_VALUE;
    }
  }
  return n;
}

/*------------------*/
/*  BUFFER-CONTROL  */
/*------------------*/
                                                             /* Check Rx-Line */
signed short sio_rxcheck (struct sio_data *dseg, unsigned char term, \
                          unsigned char dcd, unsigned char ring)
{
  signed short n;

  /* Check pointer */
  if (dseg == NULL)
    return E_SIO_NULLPTR;

  /* Status-check */
  n = sio__rxcheck (dseg, term, dcd, ring, 0);
  return n;
}

/*----*/
                                                             /* Check Tx-Line */
signed short sio_txcheck (struct sio_data *dseg, unsigned char term, \
                          unsigned char dcd, unsigned char ring)
{
  signed short n;

  /* Check pointer */
  if (dseg == NULL)
    return E_SIO_NULLPTR;

  /* Status-check */
  n = sio__txcheck (dseg, term, dcd, ring, 0);
  return n;
}

/*====*/
                                                             /* Wait Rx-Delay */
signed short sio_rxdelay (struct sio_data *dseg, unsigned char term, \
                          unsigned char dcd, unsigned char ring,     \
                          unsigned short delay)
{
  signed short n;
  DWORD d,e;

  /* Check pointer */
  if (dseg == NULL)
    return E_SIO_NULLPTR;

  /* Wait and check */
  d = GetTickCount ();
  while (42)
  {
    n = sio__rxcheck (dseg, term, dcd, ring, 0);
    if (n < 0)
      break;
    e = GetTickCount ();
    e -= d;
    if (e >= delay)
      break;
    e = delay - e;
    if (e > K_SIO_RXCHKTIME)
      e = K_SIO_RXCHKTIME;
    Sleep (e);
  }
  return n;
}

/*----*/
                                                             /* Wait Tx-Delay */
signed short sio_txdelay (struct sio_data *dseg, unsigned char term, \
                          unsigned char dcd, unsigned char ring,     \
                          unsigned short delay)
{
  signed short n;

  /* Check pointer */
  if (dseg == NULL)
    return E_SIO_NULLPTR;

  /* Wait and check */
  n = sio__txdelay (dseg, term, dcd, ring, delay);
  return n;
}

/*====*/
                                                            /* Read Rx-Errors */
signed short sio_rxerror (struct sio_data *dseg, unsigned char term, \
                          unsigned char dcd, unsigned char ring,     \
                          unsigned char clear)
{
  signed short n;

  /* Check pointer */
  if (dseg == NULL)
    return E_SIO_NULLPTR;

  /* Read rx-errors */
  n = sio__rxerror (dseg, clear);
  if (n < 0)
    return n;

  /* Status-check */
  n = sio__rxcheck (dseg, term, dcd, ring, n);
  return n;
}

/*====*/
                                                          /* Get Rx-Queue-Len */
signed short sio_rxlen (struct sio_data *dseg, unsigned char term, \
                        unsigned char dcd, unsigned char ring)
{
  unsigned long x;
  signed short n;

  /* Check pointer */
  if (dseg == NULL)
    return E_SIO_NULLPTR;

  /* Get rx-queue-len */
  n = sio__savesioerror (dseg);
  if (n < 0)
    return n;
  x = dseg->rxcachewr - dseg->rxcacherd;
  x += n;
  if (x > 32767L)
    n = 32767;
  else
    n = (signed short)x;

  /* Status-check */
  n = sio__rxcheck (dseg, term, dcd, ring, n);
  return n;
}

/*====*/
                                                            /* Clear Rx-Cache */
signed short sio_rxclear (struct sio_data *dseg, unsigned char term, \
                          unsigned char dcd, unsigned char ring)
{
  signed short n;

  /* Check pointer */
  if (dseg == NULL)
    return E_SIO_NULLPTR;

  /* Clear rx-cache */
  n = sio__rxclear (dseg);
  if (n < 0)
    return n;

  /* Status-check */
  n = sio__rxcheck (dseg, term, dcd, ring, 0);
  return n;
}

/*----*/
                                                            /* Clear Tx-Cache */
signed short sio_txclear (struct sio_data *dseg, unsigned char term, \
                          unsigned char dcd, unsigned char ring)
{
  signed short n;

  /* Check pointer */
  if (dseg == NULL)
    return E_SIO_NULLPTR;

  /* Clear tx-cache */
  n = sio__txclear (dseg);
  if (n < 0)
    return n;

  /* Status-check */
  n = sio__txcheck (dseg, term, dcd, ring, 0);
  return n;
}

/*====*/
                                                             /* Send Tx-Cache */
signed short sio_flush (struct sio_data *dseg, unsigned char term, \
                        unsigned char dcd, unsigned char ring,     \
                        unsigned char slow)
{
  signed short n;

  /* Check pointer */
  if (dseg == NULL)
    return E_SIO_NULLPTR;

  /* Send tx-cache */
  n = sio__txcheck (dseg, term, dcd, ring, 0);
  if (n < 0)
    return n;
  while (dseg->txcacheda)
  {
    n = sio__txchunk (dseg, term, dcd, ring, slow);
    if (n < 0)
      return n;
  }
  return 0;
}

/*-------------------*/
/*  READ/WRITE CHAR  */
/*-------------------*/
                                                              /* Read Rx-Byte */
signed short sio_getch (struct sio_data *dseg, unsigned char term, \
                        unsigned char dcd,  unsigned char ring,    \
                        unsigned char wait, unsigned char *data)
{
  signed short n;
  DWORD c,d,e,f;
  BOOL b;

  /* Check pointer */
  if (dseg == NULL)
    return E_SIO_NULLPTR;

  /* Reload rx-cache */
  n = sio__rxcheck (dseg, term, dcd, ring, 0);
  if (n < 0)
    return n;
  if (dseg->rxcacherd >= dseg->rxcachewr)
  {
    dseg->rxcacherd = 0;
    dseg->rxcachewr = 0;
    if (wait)
      f = K_SIO_RDTIMEOUT;
    else
      f = K_SIO_RXCHKTIME;
    d = GetTickCount ();
    while (42)
    {
      c = 0L;
      if (dseg->threads)
        EnterCriticalSection (&(dseg->winlock));
      b = ReadFile (dseg->siohnd, dseg->rxcache, K_SIO_RXCACHESIZE, &c, NULL);
      if (dseg->threads)
        LeaveCriticalSection (&(dseg->winlock));
      if (!b)
      {
        n = sio__savesioerror (dseg);
        if (n < 0)
          return n;
        c = 0L;
      }
      n = sio__rxcheck (dseg, term, dcd, ring, 0);
      if (n < 0)
        return n;
      if (c)
        break;
      e = GetTickCount ();
      e -= d;
      if (e >= f)
        break;
      if (dseg->threads)
      {
        e = f - e;
        if (e > K_SIO_RXPOLLTIME)
          e = K_SIO_RXPOLLTIME;
        Sleep (e);
        n = sio__rxcheck (dseg, term, dcd, ring, 0);
        if (n < 0)
          return n;
      }
    }
    dseg->rxcachewr = (unsigned short)c;
  }

  /* Read rx-char */
  n = 0;
  if (dseg->rxcacherd < dseg->rxcachewr)
  {
    if (data != NULL)
      *data = dseg->rxcache[dseg->rxcacherd++];
    n = 1;
  }
  return n;
}

/*----*/
                                                             /* Write Tx-Byte */
signed short sio_putch (struct sio_data *dseg, unsigned char term, \
                        unsigned char dcd, unsigned char ring,     \
                        unsigned char slow, unsigned char data)
{
  signed short n;

  /* Check pointer */
  if (dseg == NULL)
    return E_SIO_NULLPTR;

  /* Space tx-cache */
  n = sio__txcheck (dseg, term, dcd, ring, 0);
  if (n < 0)
    return n;
  while (dseg->txcacheda && (dseg->txcacherd == dseg->txcachewr))
  {
    n = sio__txchunk (dseg, term, dcd, ring, slow);
    if (n < 0)
      return n;
  }

  /* Store tx-char */
  dseg->txcache[dseg->txcachewr++] = data;
  if (dseg->txcachewr >= K_SIO_TXCACHESIZE)
    dseg->txcachewr = 0;
  dseg->txcacheda = 1;
  return 1;
}

/*============================================================================*/
                                                            /* Write Tx-Chunk */
static signed short sio__txchunk (struct sio_data *dseg, unsigned char term, \
                                  unsigned char dcd, unsigned char ring,     \
                                  unsigned char slow)
{
  unsigned short x;
  signed short n;
  DWORD c,d,e;
  BOOL b;

  if (slow)
    x = 1;
  else if (dseg->txcacherd < dseg->txcachewr)
    x = dseg->txcachewr - dseg->txcacherd;
  else
    x = K_SIO_TXCACHESIZE - dseg->txcacherd;
  d = GetTickCount ();
  while (42)
  {
    c = 0L;
    if (dseg->threads)
      EnterCriticalSection (&(dseg->winlock));
    b = WriteFile (dseg->siohnd, dseg->txcache + dseg->txcacherd, x, &c, NULL);
    if (dseg->threads)
      LeaveCriticalSection (&(dseg->winlock));
    if (!b)
    {
      n = sio__savesioerror (dseg);
      if (n < 0)
        return n;
      c = 0L;
    }
    n = sio__txcheck (dseg, term, dcd, ring, 0);
    if (n < 0)
      return n;
    if (c)
      break;
    e = GetTickCount ();
    e -= d;
    if (e >= K_SIO_TXCHKTIME)
      break;
    if (dseg->threads)
    {
      e = K_SIO_TXCHKTIME - e;
      if (e > K_SIO_TXPOLLTIME)
        e = K_SIO_TXPOLLTIME;
      Sleep (e);
      n = sio__txcheck (dseg, term, dcd, ring, 0);
      if (n < 0)
        return n;
    }
  }
  if (c)
  {
    dseg->txcacherd += (unsigned short)c;
    if (dseg->txcacherd >= K_SIO_TXCACHESIZE)
      dseg->txcacherd = 0;
    if (dseg->txcacherd == dseg->txcachewr)
    {
      dseg->txcacheda = 0;
      dseg->txcacherd = 0;
      dseg->txcachewr = 0;
    }
    if (slow)
    {
      Sleep (slow);
      n = sio__txcheck (dseg, term, dcd, ring, 0);
      if (n < 0)
        return n;
    }
  }
  return 0;
}

/*====*/
                                                             /* Wait Tx-Delay */
static signed short sio__txdelay (struct sio_data *dseg, unsigned char term, \
                                  unsigned char dcd, unsigned char ring,     \
                                  unsigned short delay)
{
  signed short n;
  DWORD d,e;

  /* Wait and check */
  d = GetTickCount ();
  while (42)
  {
    n = sio__txcheck (dseg, term, dcd, ring, 0);
    if (n < 0)
      break;
    e = GetTickCount ();
    e -= d;
    if (e >= delay)
      break;
    e = delay - e;
    if (e > K_SIO_TXCHKTIME)
      e = K_SIO_TXCHKTIME;
    Sleep (e);
  }
  return n;
}

/*====*/
                                                        /* Check Rx-Handshake */
static signed short sio__rxcheck (struct sio_data *dseg, unsigned char term, \
                                  unsigned char dcd, unsigned char ring,     \
                                  signed short retval)
{
  signed short n,m;

  n = sio__chkctrlwires (dseg, term, dcd, ring);
  if ((n <= E_SIO_LINE_SIGTERM) && (n >= E_SIO_LINE_NODCD))
  {
    m = sio__rxerror (dseg, 1);
    if (m < 0)
      return m;
    m = sio__rxclear (dseg);
    if (m < 0)
      return m;
  }
  else if (n >= 0)
    n = retval;
  return n;
}

/*----*/
                                                        /* Check Tx-Handshake */
static signed short sio__txcheck (struct sio_data *dseg, unsigned char term, \
                                  unsigned char dcd, unsigned char ring,     \
                                  signed short retval)
{
  signed short n,m;

  n = sio__chkctrlwires (dseg, term, dcd, ring);
  if ((n <= E_SIO_LINE_SIGTERM) && (n >= E_SIO_LINE_NODCD))
  {
    m = sio__txclear (dseg);
    if (m < 0)
      return m;
  }
  else if (n >= 0)
    n = retval;
  return n;
}

/*====*/

static signed short sio__rxclear (struct sio_data *dseg)    /* Clear Rx-Cache */
{
  BOOL b;

  dseg->rxcacherd = 0;
  dseg->rxcachewr = 0;
  if (dseg->threads)
    EnterCriticalSection (&(dseg->winlock));
  b = PurgeComm (dseg->siohnd, PURGE_RXCLEAR);
  if (dseg->threads)
    LeaveCriticalSection (&(dseg->winlock));
  if (!b)
    return E_SIO_RXCLR_PURGE;
  return 0;
}

/*----*/

static signed short sio__txclear (struct sio_data *dseg)    /* Clear Tx-Cache */
{
  BOOL b;

  dseg->txcacheda = 0;
  dseg->txcacherd = 0;
  dseg->txcachewr = 0;
  if (dseg->threads)
    EnterCriticalSection (&(dseg->winlock));
  b = PurgeComm (dseg->siohnd, PURGE_TXCLEAR);
  if (dseg->threads)
    LeaveCriticalSection (&(dseg->winlock));
  if (!b)
    return E_SIO_TXCLR_PURGE;
  return 0;
}

/*====*/
                                                            /* Read Rx-Errors */
static signed short sio__rxerror (struct sio_data *dseg, unsigned char clear)
{
  unsigned char x;
  signed short n;

  if (dseg->threads)
    EnterCriticalSection (&(dseg->errlock));
  n = sio__savesioerror (dseg);
  x = dseg->rxerror;
  if (clear)
    dseg->rxerror = 0;
  if (dseg->threads)
    LeaveCriticalSection (&(dseg->errlock));
  if (n >= 0)
    n = x;
  return n;
}

/*----*/
                                                           /* Save Sio-Errors */
static signed short sio__savesioerror (struct sio_data *dseg)
{
  unsigned char m,x;
  signed short n;
  COMSTAT stat;
  DWORD e,f;
  BOOL b;

  e = 0L;
  if (dseg->threads)
  {
    EnterCriticalSection (&(dseg->errlock));
    EnterCriticalSection (&(dseg->winlock));
  }
  b = ClearCommError (dseg->siohnd, &e, &stat);
  if (dseg->threads)
    LeaveCriticalSection (&(dseg->winlock));
#ifdef _WIN32_WCE
  e &= ~CE_RXOVER;
#endif
  if (b && e)
  {
    m = 0x01;
    for (x=0; x<K_SIO_ERRORFLAGS; x++)
    {
      f = sio__errorflag[x];
      if (e & f)
      {
        e &= ~f;
        dseg->rxerror |= m;
      }
      m <<= 1;
    }
  }
  if (dseg->threads)
    LeaveCriticalSection (&(dseg->errlock));
  if (b)
  {
    if (e)
      n = E_SIO_STATE_BADFLAG;
    else if (stat.cbInQue > 32767L)
      n = 32767;
    else
      n = (signed short)stat.cbInQue;
  }
  else
    n = E_SIO_STATE_GETSTATE;
  return n;
}

/*====*/
                                                       /* Check Control-Wires */
static signed short sio__chkctrlwires (struct sio_data *dseg,                 \
                                       unsigned char term, unsigned char dcd, \
                                       unsigned char ring)
{
  DWORD s;
  BOOL b;

  if (term && (dseg->cfg.sigterm != NULL))
    if (*(dseg->cfg.sigterm))
      return E_SIO_LINE_SIGTERM;
  if (dseg->cfg.usedsr || (dseg->cfg.usedcd && dcd) \
   || (dseg->cfg.usering && ring))
  {
    s = 0L;
    if (dseg->threads)
      EnterCriticalSection (&(dseg->winlock));
    b = GetCommModemStatus (dseg->siohnd, &s);
    if (dseg->threads)
      LeaveCriticalSection (&(dseg->winlock));
    if (!b)
      return E_SIO_LINE_GETLINE;
    if (dseg->cfg.usedsr && !(s & MS_DSR_ON))
      return E_SIO_LINE_NODSR;
    if (dseg->cfg.usedcd && dcd && !(s & MS_RLSD_ON))
      return E_SIO_LINE_NODCD;
    if (dseg->cfg.usering && ring && (s & MS_RING_ON))
      return E_SIO_LINE_RINGING;
  }
  return 0;
}

/*====*/

static signed short sio__rilsync (struct sio_data *dseg)          /* Sync Ril */
{
  HRESULT r;
  DWORD d;
  BOOL b;

  /* Sync ril */
  b = ResetEvent (dseg->riljobevt);
  if (!b)
    return -1;
  r = (dseg->ril_regs)(dseg->rilusrhnd);
  if (r < 0)
    return -2;
  d = WaitForSingleObject (dseg->riljobevt, K_SIO_RILTIME);
  if (d == WAIT_TIMEOUT)
    return -3;
  return 0;
}

/*----*/
                                                               /* Ril Cmd-end */
static void CALLBACK sio__rilresult (DWORD index, HRESULT cmdid, \
                                     const void *data, DWORD dlen, DWORD par)
{
  struct sio_data *dseg;

  /* Set ril-event */
  dseg = (struct sio_data *)par;
  SetEvent (dseg->riljobevt);
}


/*** EOF ***/
